// UsersService.cs
// Facebook/Framework/Service
// Copyright (c) 2007, Nikhil Kothari. All Rights Reserved.
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Facebook.Service.Core;

namespace Facebook.Service {

    /// <summary>
    /// Represents the service APIs that allow access to information on Facebook users.
    /// </summary>
    public sealed class UsersService {

        private FacebookSession _session;
		private List<string> unrestrictedFields = new List<string>(User.UnrestrictedFields.Split(','));

        internal UsersService(FacebookSession session) {
            Debug.Assert(session != null);
            _session = session;
        }

		private string GetAllowedFieldList(string fields)
		{
			string[] fieldChoices = fields.Split(',');
			List<string> allowedFields = new List<string>();
			foreach (string field in fieldChoices)
			{
				if (unrestrictedFields.Contains(field))
				{
					allowedFields.Add(field);
				}
			}

			string approvedFieldList = "";
			foreach (string field in allowedFields)
			{
				approvedFieldList += ("," + field);
			}
			return (approvedFieldList.Remove(0, 1));
		}

        /// <summary>
        /// Gets the desired information about the specified user ID. This corresponds to the
        /// users.getInfo Facebook method.
        /// </summary>
        /// <param name="userID">The requested user ID, or null to get information about the current user.</param>
        /// <param name="fields">The comma-separated list of fields, or null to get all known fields.</param>
        /// <returns>The information about the specific user; null if the call fails.</returns>
        public User GetUser(string userID, string fields)
		{
			return GetUser(userID, fields, false);
		}
		/// <summary>
		/// Gets the desired information about the specified user ID. This corresponds to the
		/// users.getInfo and users.getStandardInfo Facebook methods.
		/// </summary>
		/// <param name="userID">The requested user ID, or null to get information about the current user.</param>
		/// <param name="fields">The comma-separated list of fields, or null to get all known fields.</param>
		/// <param name="restricted">
		/// Boolean indicating some measure of presumption of the users not having authorized the application.
		/// </param>
		/// <returns>The information about the specific user; null if the call fails.</returns>
		public User GetUser(string userID, string fields, bool restricted)
		{
            if (String.IsNullOrEmpty(userID)) {
                userID = _session.UserID;
            }
            if (!restricted && String.IsNullOrEmpty(fields))
			{
                fields = User.AllFields;
            }
			else if (restricted && String.IsNullOrEmpty(fields))
			{
				fields = User.UnrestrictedFields;
			}
			else if (restricted && !String.IsNullOrEmpty(fields))
			{
				fields = GetAllowedFieldList(fields);
			}

            FacebookRequest request = new FacebookRequest(_session);
            request.Parameters["uids"] = userID;
            request.Parameters["fields"] = fields;

			// Call the getStandardInfo if we suspect that the user under consideration
			// is going to be restricted in what they'll return.
            FacebookResponse response = (restricted) ? request.InvokeMethod("users.getStandardInfo") : request.InvokeMethod("users.getInfo");
            if (response.Status == FacebookResponseStatus.Succeeded) {
                if (response.Result is ArrayList) {
                    Debug.Assert(response.Result is ArrayList);
                    ArrayList userInfoList = (ArrayList)response.Result;

                    Debug.Assert(userInfoList.Count == 1);
                    Debug.Assert(userInfoList[0] is Hashtable);
                    return new User((Hashtable)userInfoList[0]);
                }
                else if (response.Result is Hashtable) {
                    return new User((Hashtable)response.Result);
                }
            }
			
            return null;
        }

        /// <summary>
        /// Gets the desired information about the specified user IDs. This corresponds to the
        /// users.getInfo Facebook method.
        /// </summary>
        /// <param name="userIDs">The list of requested user IDs.</param>
        /// <param name="fields">The comma-separated list of fields, or null to get all known fields.</param>
        /// <returns>The information about the specified users; null if the call fails.</returns>
        public ICollection<User> GetUsers(ICollection<string> userIDs, string fields)
		{
			return GetUsers(userIDs, fields, false);
		}
		/// <summary>
		/// Gets the desired information about the specified user IDs. This corresponds to the
		/// users.getInfo and users.getStandardInfo Facebook methods.
		/// </summary>
		/// <param name="userIDs">The list of requested user IDs.</param>
		/// <param name="fields">The comma-separated list of fields, or null to get all known fields.</param>
		/// <param name="restricted">
		/// Boolean indicating some measure of presumption of the users not having authorized the application.
		/// </param>
		/// <returns>The information about the specified users; null if the call fails.</returns>
		public ICollection<User> GetUsers(ICollection<string> userIDs, string fields, bool restricted)
		{
            if ((userIDs == null) || (userIDs.Count == 0)) {
                throw new ArgumentNullException("userIDs");
			}
			if (!restricted && String.IsNullOrEmpty(fields))
			{
				fields = User.AllFields;
			}
			else if (restricted && String.IsNullOrEmpty(fields))
			{
				fields = User.UnrestrictedFields;
			}
			else if (restricted && !String.IsNullOrEmpty(fields))
			{
				fields = GetAllowedFieldList(fields);
			}

            StringBuilder uidBuilder = new StringBuilder();
            foreach (string id in userIDs) {
                if (uidBuilder.Length != 0) {
                    uidBuilder.Append(",");
                }
                uidBuilder.Append(id);
            }

            FacebookRequest request = new FacebookRequest(_session);
            request.Parameters["uids"] = uidBuilder.ToString();
            request.Parameters["fields"] = fields;

            FacebookResponse response = (restricted) ? request.InvokeMethod("users.getStandardInfo") : request.InvokeMethod("users.getInfo");
            if (response.Status == FacebookResponseStatus.Succeeded) {
                List<User> users;

                if (response.Result is ArrayList) {
                    ArrayList userInfoList = (ArrayList)response.Result;

                    users = new List<User>(userInfoList.Count);
                    foreach (Hashtable userState in userInfoList) {
                        users.Add(new User((Hashtable)userState));
                    }
                }
                else if (response.Result is Hashtable) {
                    users = new List<User>(1);
                    users.Add(new User((Hashtable)response.Result));
                }
                else {
                    users = new List<User>(0);
                }

                return users;
            }

            return null;
        }

		/// <summary>
		/// Indicates whether the user (either the session user or user
		/// specified by <paramref name="userID"/>) has authorized the
		/// calling application.
		/// </summary>
		/// <param name="userID">
		/// The user ID of the user who may have authorized the
		/// application. If this parameter is empty or null, then it
		/// defaults to the session user.
		/// </param>
		/// <returns>
		/// Boolean indicating whether the specified user has authorized
		/// the application.
		/// </returns>
		public bool IsAppUser(string userID)
		{
			// Desktop applications require session key for this call
			FacebookRequest request = new FacebookRequest(_session, (_session.ApplicationType == FacebookApplicationType.Client));
			if (_session.ApplicationType == FacebookApplicationType.Web)
			{
				// If a user ID is provided, use it. Otherwise, go with the logged-in user.
				request.Parameters["uid"] = (String.IsNullOrEmpty(userID)) ? _session.UserID : userID;
			}

			FacebookResponse response = request.InvokeMethod("users.isAppUser");
			if (response.Status == FacebookResponseStatus.Succeeded)
			{
				return FacebookResponse.CoerceBoolean(response.Result);
			}

			return false;
		}

        /// <summary>
        /// Checks if the current user is the administrator of the current page
        /// that the application is running within.
        /// </summary>
        /// <returns>true if the user is an admin; false otherwise.</returns>
        public bool IsPageAdmin() {
            if (_session.SessionType != FacebookSessionType.PresencePage) {
                throw new InvalidOperationException("The current application is not running on a Facebook page.");
            }

            return IsPageAdmin(_session.UserID);
        }

        /// <summary>
        /// Checks if the current user is the administrator of the specified page.
        /// </summary>
        /// <param name="pageID">The ID of the specified page.</param>
        /// <returns>true if the user is an admin; false otherwise.</returns>
        public bool IsPageAdmin(string pageID) {
            if (String.IsNullOrEmpty(pageID)) {
                throw new ArgumentNullException("pageID");
            }

            FacebookRequest request = new FacebookRequest(_session);
            request.Parameters["page_id"] = pageID;

            FacebookResponse response = request.InvokeMethod("pages.isAdmin");
            if (response.Status == FacebookResponseStatus.Succeeded) {
                return FacebookResponse.CoerceBoolean(response.Result);
            }

            return false;
        }

        /// <summary>
        /// Checks if the current user is a fan of the specified page.
        /// </summary>
        /// <param name="pageID">The ID of the specified page.</param>
        /// <returns>true if the user is a fan; false otherwise.</returns>
        public bool IsPageFan(string pageID) {
            return IsPageFan(pageID, null);
        }

        /// <summary>
        /// Checks if the specified user is a fan of the specified page.
        /// </summary>
        /// <param name="pageID">The ID of the specified page.</param>
        /// <param name="userID">The ID of the specified user.</param>
        /// <returns>true if the user is a fan; false otherwise.</returns>
        /// <returns></returns>
        public bool IsPageFan(string pageID, string userID) {
            if (String.IsNullOrEmpty(pageID)) {
                throw new ArgumentNullException("pageID");
            }

            if (String.IsNullOrEmpty(userID)) {
                if (_session.SessionType == FacebookSessionType.User) {
                    userID = _session.UserID;
                }
                else {
                    userID = _session.PageUserID;
                }
            }

            FacebookRequest request = new FacebookRequest(_session);
            request.Parameters["page_id"] = pageID;
            request.Parameters["uid"] = userID;

            FacebookResponse response = request.InvokeMethod("pages.isFan");
            if (response.Status == FacebookResponseStatus.Succeeded) {
                return FacebookResponse.CoerceBoolean(response.Result);
            }

            return false;
        }

        /// <summary>
        /// Sets the current user's status message. If the specified status is
        /// empty, the status message is cleared instead.
        /// </summary>
        /// <param name="status">The message to set.</param>
        /// <returns>true if the message was set; false otherwise.</returns>
        public bool SetStatus(string status) {
            bool dummy;
            return SetStatus(status, /* includesVerb */ false, out dummy);
        }

        /// <summary>
        /// Sets the current user's status message. If the specified status is
        /// empty, the status message is cleared instead.
        /// The value of includesVerb determines if Facebook should prefix the status
        /// with the leading "is" or not.
        /// </summary>
        /// <param name="status">The message to set.</param>
        /// <param name="includesVerb">True if the status includes the verb and the default 'is' should be disabled; false otherwise.</param>
        /// <returns>true if the message was set; false otherwise.</returns>
        public bool SetStatus(string status, bool includesVerb) {
            bool dummy;
            return SetStatus(status, includesVerb, out dummy);
        }

        /// <summary>
        /// Sets the current user's status message. If the specified status is
        /// empty, the status message is cleared instead.
        /// </summary>
        /// <param name="status">The message to set.</param>
        /// <param name="permissionNotGranted">Indicates whether the application does not have permissions to set the status.</param>
        /// <returns>true if the message was set; false otherwise.</returns>
        [Obsolete("Use the SetStatus overload with the includesVerb parameter")]
        public bool SetStatus(string status, out bool permissionNotGranted) {
            return SetStatus(status, /* includesVerb */ false, out permissionNotGranted);
        }

        /// <summary>
        /// Sets the current user's status message. If the specified status is
        /// empty, the status message is cleared instead.
        /// The value of includesVerb determines if Facebook should prefix the status
        /// with the leading "is" or not.
        /// </summary>
        /// <param name="status">The message to set.</param>
        /// <param name="includesVerb">True if the status includes the verb and the default 'is' should be disabled; false otherwise.</param>
        /// <param name="permissionNotGranted">Indicates whether the application does not have permissions to set the status.</param>
        /// <returns>true if the message was set; false otherwise.</returns>
		public bool SetStatus(string status, bool includesVerb, out bool permissionNotGranted)
		{
			return SetStatus(null, status, includesVerb, out permissionNotGranted);
		}
        public bool SetStatus(string userID, string status, bool includesVerb, out bool permissionNotGranted)
		{
			if (String.IsNullOrEmpty(userID))
			{
				// Always set userID, because in the case of a Facebook page session,
				// not passing the userID has the unexpected result of Facebook
				// trying to use the UserID of the user browsing the page instead,
				// and not the page ID.

				userID = _session.UserID;
			}
            permissionNotGranted = false;
			bool isDesktopApp = (_session.ApplicationType == FacebookApplicationType.Client);

			// Session key is only required for desktop applications.
            FacebookRequest request = new FacebookRequest(_session, isDesktopApp);
			if (!isDesktopApp)
			{
				request.Parameters["uid"] = userID;
			}
            if (String.IsNullOrEmpty(status)) {
                request.Parameters["clear"] = "1";
            }
            else {
                request.Parameters["status"] = status;
            }
            if (includesVerb) {
                request.Parameters["status_includes_verb"] = "1";
            }

            FacebookResponse response = request.InvokeMethod("users.setStatus");
            if (response.Status == FacebookResponseStatus.PermissionNotGranted) {
                permissionNotGranted = true;
                return false;
            }
            else if (response.Status == FacebookResponseStatus.Succeeded) {
                Debug.Assert(response.Result is bool);
                return (bool)response.Result;
            }

            return false;
        }
    }
}
